/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using DaveSexton.Controls;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	public sealed class TopicExplorer : ToolWindow, ITopicHierarchy
	{
		#region Public Properties
		public static readonly Guid ToolWindowGuid = new Guid("{3CCB6E3A-C331-4f9e-90C5-141A99F81667}");
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
		public static readonly TopicExplorer Instance = new TopicExplorer();

		public override System.Windows.Forms.Control Control
		{
			get
			{
				return control;
			}
		}

		public override string Caption
		{
			get
			{
				if (currentProject == null)
					return Resources.Text.TopicExplorerToolWindowCaption;
				else
					return Resources.Text.TopicExplorerToolWindowCaption + " - " + currentProject.Name;
			}
		}

		public override System.Drawing.Bitmap Image
		{
			get
			{
				return Resources.Images.TopicManagement_ToolWindowIcon;
			}
		}

		public override bool IsLinkable
		{
			get
			{
				return true;
			}
		}

		public static TopicManager CurrentManager
		{
			get
			{
				return currentManager;
			}
		}

		public static IDocProject CurrentProject
		{
			get
			{
				return currentProject;
			}
		}

		public static Topic CurrentTopic
		{
			get
			{
				return control.CurrentTopic;
			}
		}

		public static TopicNode SelectedNode
		{
			get
			{
				return control.SelectedNode;
			}
			set
			{
				control.SelectedNode = value;
			}
		}

		public static TopicNode FirstNode
		{
			get
			{
				return control.FirstNode;
			}
		}
		#endregion

		#region Private / Protected
		private static readonly TopicExplorerControl control = CreateControl();
		private static readonly EnvDTE.SelectionEvents selectionEvents = CreateSelectionEvents();

		private static TopicManager currentManager;
		private static IDocProject currentProject;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicExplorer" /> class.
		/// </summary>
		private TopicExplorer()
			: base(ToolWindowGuid)
		{
		}
		#endregion

		#region Methods
		private static EnvDTE.SelectionEvents CreateSelectionEvents()
		{
			if (DocProjectEnvironment.RunningInVisualStudio)
				return VSEnvironmentHost.DTE.Events.SelectionEvents;
			else
				return null;
		}

		private static TopicExplorerControl CreateControl()
		{
			TopicExplorerControl ctrl = new TopicExplorerControl();
			ctrl.SelectedNodeChanged += control_SelectedTopicChanged;
			ctrl.Edit += control_TopicDoubleClick;
			ctrl.Properties += control_Properties;

			return ctrl;
		}

		public static void NotifyDroppedNodeInEditor()
		{
			control.NotifyDroppedNodeInEditor();
		}

		public static string CreateReferenceLink(TopicNode node)
		{
			return TopicExplorerControl.CreateReferenceLink(node);
		}

		public static string CreateConceptualLink(TopicNode node)
		{
			return TopicExplorerControl.CreateConceptualLink(node);
		}

		public static void Refresh()
		{
			control.Reload();
		}

		public static void SyncWithSolutionExplorer()
		{
			IDocProject selected = DocProjectEnvironment.SelectedProject;

			if (selected != null && !SandcastleBuildEngineProvider.IsProjectValid(selected))
				// In testing, this method is called when Visual Studio's SelectionEevnts.OnChange event is raised, but if the selected project is 
				// a new project (i.e., the New Project Wizard hasn't completed yet) then synchronization must not occur.
				currentProject = null;
			else if ((currentProject == null && selected == null)
				|| (currentProject != null && selected != null && currentProject.Matches(selected)))
			{
				if (currentProject != selected)
					currentProject = selected;
				return;
			}
			else
				currentProject = selected;

			Instance.Window.Caption = Instance.Caption;

			if (currentProject == null)
				currentManager = null;
			else if (currentManager == null || currentProject != currentManager.Project)
				currentManager = ((SandcastleProjectOptions) currentProject.Options).TopicManagement;
			else
				return;

			control.Manager = currentManager;

			OnCurrentManagerChanged(EventArgs.Empty);

			if (currentManager != null)
				// NOTE: control.Manager must be assigned before LoadTopics is invoked
				currentManager.LoadTopics(false);
		}

		public static void SuspendLayout()
		{
			control.SuspendLayout();
		}

		public static void ResumeLayout(bool performLayout)
		{
			control.ResumeLayout(performLayout);
		}

		public static void BeginUpdate()
		{
			control.BeginUpdate();
		}

		public static void EndUpdate()
		{
			control.EndUpdate();
		}

		public static TopicNode GetNodeById(string id)
		{
			return control.GetNodeById(id);
		}

		public static void ForEachNode(Action<TopicNode> action)
		{
			control.ForEachNode(action);
		}

		public static TopicNode FindTopic(TopicNode startAfter, Predicate<TopicNode> predicate)
		{
			return control.FindTopic(startAfter, predicate);
		}

		public static TopicNode FindTopic(Predicate<TopicNode> predicate)
		{
			return control.FindTopic(predicate);
		}

		public static TopicNode FindTopic(System.Xml.XPath.IXPathNavigable element)
		{
			return control.FindTopic(element);
		}

		public static IEnumerable<TopicNode> FindTopics(Predicate<TopicNode> predicate)
		{
			return control.FindTopics(predicate);
		}

		public static TopicNode SelectNextTopic(TopicNode from)
		{
			return control.SelectNextTopic(from);
		}

		public static void SelectNextTopic(Predicate<TopicNode> predicate)
		{
			control.SelectNextTopic(predicate);
		}
		#endregion

		#region Events
		private static readonly object CurrentManagerChangedEventLock = new object();
		private static EventHandler CurrentManagerChangedEvent;
		private static readonly object CurrentTopicChangedEventLock = new object();
		private static EventHandler CurrentTopicChangedEvent;

		/// <summary>
		/// Event raised when the <see cref="CurrentTopic"/> property value changes.
		/// </summary>
		public static event EventHandler CurrentTopicChanged
		{
			add
			{
				lock (CurrentTopicChangedEventLock)
				{
					CurrentTopicChangedEvent += value;
				}
			}
			remove
			{
				lock (CurrentTopicChangedEventLock)
				{
					CurrentTopicChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="CurrentTopicChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private static void OnCurrentTopicChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (CurrentTopicChangedEventLock)
			{
				handler = CurrentTopicChangedEvent;
			}

			if (handler != null)
				handler(null, e);
		}

		/// <summary>
		/// Event raised when the <see cref="CurrentManager"/> property value changes.
		/// </summary>
		public static event EventHandler CurrentManagerChanged
		{
			add
			{
				lock (CurrentManagerChangedEventLock)
				{
					CurrentManagerChangedEvent += value;
				}
			}
			remove
			{
				lock (CurrentManagerChangedEventLock)
				{
					CurrentManagerChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="CurrentManagerChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private static void OnCurrentManagerChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (CurrentManagerChangedEventLock)
			{
				handler = CurrentManagerChangedEvent;
			}

			if (handler != null)
				handler(null, e);
		}
		#endregion

		#region Event Handlers
		private static void SelectionEvents_OnChange()
		{
			if (Instance.Visible)
				SyncWithSolutionExplorer();
		}

		private static void control_SelectedTopicChanged(object sender, EventArgs e)
		{
			OnCurrentTopicChanged(e);
		}

		private static void control_TopicDoubleClick(object sender, TopicNodeEventArgs e)
		{
			Topic topic = e.Node.Topic;

			switch (topic.TopicType)
			{
				case TopicType.Container:
					// do nothing
					break;
				case TopicType.Html:
				case TopicType.Conceptual:
					control.Manager.OpenConceptualTopic(topic);
					break;
				default:
					TopicEditorToolWindow.Instance.Visible = true;
					break;
			}
		}

		private static void control_Properties(object sender, TopicNodeEventArgs e)
		{
			TopicPropertiesToolWindow.Instance.Visible = true;
		}

		protected override void OnWindowCreated(EventArgs e)
		{
			if (selectionEvents != null)
				selectionEvents.OnChange += SelectionEvents_OnChange;

			base.OnWindowCreated(e);
		}

		protected override void OnWindowDestroyed(EventArgs e)
		{
			if (selectionEvents != null)
				selectionEvents.OnChange -= SelectionEvents_OnChange;

			base.OnWindowDestroyed(e);
		}

		protected override void OnShow(EventArgs e)
		{
			SyncWithSolutionExplorer();

			base.OnShow(e);
		}
		#endregion

		#region ITopicHierarchy Members
		Topic ITopicHierarchy.CurrentTopic
		{
			get
			{
				return control.CurrentTopic;
			}
		}

		TopicNode ITopicHierarchy.SelectedNode
		{
			get
			{
				return control.SelectedNode;
			}
			set
			{
				control.SelectedNode = value;
			}
		}

		TopicNode ITopicHierarchy.FirstNode
		{
			get
			{
				return control.FirstNode;
			}
		}

		TopicNode ITopicHierarchy.GetNodeById(string id)
		{
			return control.GetNodeById(id);
		}

		void ITopicHierarchy.ForEachNode(Action<TopicNode> action)
		{
			control.ForEachNode(action);
		}

		TopicNode ITopicHierarchy.FindTopic(TopicNode startAfter, Predicate<TopicNode> predicate)
		{
			return control.FindTopic(startAfter, predicate);
		}

		TopicNode ITopicHierarchy.FindTopic(Predicate<TopicNode> predicate)
		{
			return control.FindTopic(predicate);
		}

		TopicNode ITopicHierarchy.FindTopic(System.Xml.XPath.IXPathNavigable element)
		{
			return control.FindTopic(element);
		}

		IEnumerable<TopicNode> ITopicHierarchy.FindTopics(Predicate<TopicNode> predicate)
		{
			return control.FindTopics(predicate);
		}

		TopicNode ITopicHierarchy.SelectNextTopic(TopicNode from)
		{
			return control.SelectNextTopic(from);
		}

		string ITopicHierarchy.CreateReferenceLink(TopicNode node)
		{
			return TopicExplorerControl.CreateReferenceLink(node);
		}

		string ITopicHierarchy.CreateConceptualLink(TopicNode node)
		{
			return TopicExplorerControl.CreateConceptualLink(node);
		}

		void ITopicHierarchy.SelectNextTopic(Predicate<TopicNode> predicate)
		{
			control.SelectNextTopic(predicate);
		}

		void ITopicHierarchy.NotifyDroppedNodeInEditor()
		{
			control.NotifyDroppedNodeInEditor();
		}

		void ITopicHierarchy.Refresh()
		{
			control.Refresh();
		}

		void ITopicHierarchy.SuspendLayout()
		{
			control.SuspendLayout();
		}

		void ITopicHierarchy.ResumeLayout(bool performLayout)
		{
			control.ResumeLayout(performLayout);
		}

		void ITopicHierarchy.BeginUpdate()
		{
			control.BeginUpdate();
		}

		void ITopicHierarchy.EndUpdate()
		{
			control.EndUpdate();
		}
		#endregion
	}
}
